package de.mmis.core.base.infrastructure;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import de.mmis.core.base.Annotations.NoProperty;
import de.mmis.core.base.DeserializableAnnotations.ConstructorDeserializable;
import de.mmis.core.base.DeserializableAnnotations.DeserializationConstructor;
import de.mmis.core.base.MethodDescriptor.P;
import de.mmis.core.base.abstracttree.Tree;

/**
 * This class stores all fields of published Devices. This contains: <br />
 * <ul>
 * <li>Name / ID of the object</li>
 * <li>implemented interfaces</li>
 * <li>device Host, i. e. the machine were the instance was created</li>
 * <li>type of publishing (see {@link PublishType})</li>
 * <li>host and port where the proxy has to connect</li>
 * </ul>
 * 
 * When one of the fields name, device host, type or host is <b>null</b>, or
 * when port is -1, the Description is used as a search pattern. The method
 * {@link #isPattern()} can be used to verify this.<br />
 * A Description matches a pattern iff
 * <ul>
 * <li>all non-null fields (except the interfaces-list) in the pattern are
 * equals the corresponding field of the description and</li>
 * <li>the interfaces-list of the description contains all interfaces of the
 * pattern</li>
 * </ul>
 * 
 * @author Martin Nyolt
 * 
 */
@ConstructorDeserializable(typeIdentifier = "infrastructure/published-device-description")
public class PublishedDeviceDescription {

	public static enum PublishType {
		SOCKET, HTTP, TELNET, TUPLESPACE,
		/**
		 * Publish / Subscribe<br/>
		 * Host and port are ignored
		 */
		PUBSUB
	}

	/** the ID of the device */
	private String id;

	/** A name to identify the host */
	private String deviceHost;
	private PublishedDeviceDescription.PublishType type;

	/** where to find the published device */
	private String hostIP;

	/** where to find the published device */
	private int port;

	private Map<String, Object> properties;

	// no deserialization constructor
	public PublishedDeviceDescription(String id, String deviceHost,
			PublishType type, InetAddress host, int port) {
		this(id, deviceHost, type, host == null ? null : host.getHostAddress(),
				port, null);
	}

	/**
	 * The device is hosted on the deviceHost and published as the type on
	 * host:port. Use <b>null</b> values (or -1 for port) to create a search
	 * pattern.
	 * 
	 */
	@DeserializationConstructor
	public PublishedDeviceDescription(@P("ID") String id,
			@P("DeviceHost") String deviceHost, @P("Type") PublishType type,
			@P("HostIP") String hostIP, @P("Port") int port,
			@P("Properties") Map<String, Object> properties) {
		this.id = id;
		this.deviceHost = deviceHost;
		this.type = type;
		this.hostIP = hostIP;
		this.port = port;

		if (properties != null)
			this.properties = properties;
		else
			this.properties = new HashMap<String, Object>();
	}

	/**
	 * @return the name
	 */
	public String getID() {
		return this.id;
	}

	public void setId(String id) {
		this.id = id;
	}

	/**
	 * @return the type
	 */
	public PublishType getType() {
		return this.type;
	}

	public void setType(PublishedDeviceDescription.PublishType type) {
		this.type = type;
	}

	/**
	 * @return the deviceHost, a textual hostname for human only
	 */
	public String getDeviceHost() {
		return this.deviceHost;
	}

	public void setDeviceHost(String deviceHost) {
		this.deviceHost = deviceHost;
	}

	public String getHostIP() {
		return hostIP;
	}

	public void setHostIP(String hostIP) {
		this.hostIP = hostIP;
	}

	@NoProperty
	public InetAddress getHost() {
		if (hostIP == null)
			return null;

		try {
			return InetAddress.getByName(hostIP);
		} catch (UnknownHostException e) {
			return null;
		}
	}

	/**
	 * @return the port
	 */
	public int getPort() {
		return this.port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public Map<String, Object> getProperties() {
		return properties;
	}

	public void setProperties(Map<String, Object> properties) {
		this.properties = properties;
	}

	/**
	 * Tests if this device matches the given pattern
	 * 
	 * @param pattern
	 */
	public boolean matches(PublishedDeviceDescription pattern) {
		if (pattern.id != null && !pattern.id.equals(this.id))
			return false;

		if (pattern.type != null && !pattern.type.equals(this.type))
			return false;

		if (pattern.deviceHost != null
				&& !pattern.deviceHost.equals(this.deviceHost))
			return false;

		// pattern.properties must be a subset if this.properties
		if (!properties.entrySet().containsAll(pattern.properties.entrySet()))
			return false;

		if (this.type == PublishType.PUBSUB)
			// ignore host and port
			return true;

		if (pattern.hostIP != null && !pattern.hostIP.equals(this.hostIP))
			return false;

		if (pattern.port != -1 && pattern.port != this.port)
			return false;

		return true;
	}

	@Override
	public String toString() {
		return Tree.fromObject(this, false).toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;

		PublishedDeviceDescription other = (PublishedDeviceDescription) obj;

		if (deviceHost == null) {
			if (other.deviceHost != null)
				return false;
		} else if (!deviceHost.equals(other.deviceHost))
			return false;

		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;

		if (type != other.type)
			return false;

		if (properties == null) {
			if (other.properties != null)
				return false;
		} else if (!properties.equals(other.properties))
			return false;

		if (type == PublishType.PUBSUB)
			// ignore host and port
			return true;

		if (hostIP == null) {
			if (other.hostIP != null)
				return false;
		} else if (!hostIP.equals(other.hostIP))
			return false;

		if (port != other.port)
			return false;

		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((deviceHost == null) ? 0 : deviceHost.hashCode());
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		result = prime * result
				+ ((properties == null) ? 0 : properties.hashCode());

		if (type == PublishType.PUBSUB)
			// ignore host and port
			return result;

		result = prime * result + ((hostIP == null) ? 0 : hostIP.hashCode());
		result = prime * result + port;

		return result;
	}

	/**
	 * @return true, if this description contains <b>null</b>-values
	 */
	@NoProperty
	public boolean isPattern() {
		return this.id == null || this.deviceHost == null || this.type == null
				|| this.hostIP == null || this.port == -1;
	}

}
